﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Noris.Tools.TraceVisualiser.Support;
using System.Windows.Forms;

namespace Noris.Tools.TraceDownloader.Downloader
{
    /// <summary>
    /// Třída obsahující jeden preset = jedna konfigurace
    /// </summary>
    internal class Preset
    {
        #region Konstrukce a clear
        /// <summary>
        /// Konstruktor
        /// </summary>
        internal Preset()
        {
            this._InputList = new List<PresetInputItem>();
            this.Clear();
        }
        /// <summary>
        /// Vytvoří a vrátí preset z dodaného XML elementu
        /// </summary>
        /// <param name="element"></param>
        /// <returns></returns>
        internal static Preset CreateFrom(XElement element)
        {
            if (element == null || element.Name.LocalName != "preset") return null;
            Preset preset = new Preset();
            preset.Serial = element;
            return preset;
        }
        /// <summary>
        /// Vytvoří a vrátí preset jako kopii z dodaného vzoru
        /// </summary>
        /// <param name="original"></param>
        /// <returns></returns>
        internal static Preset CreateFrom(Preset original)
        {
            if (original == null) return null;
            Preset preset = new Preset();
            preset.Serial = original.Serial;
            preset.ID = "";
            return preset;
        }
        /// <summary>
        /// Vytvoří a vrátí implicitní preset
        /// </summary>
        /// <returns></returns>
        internal static Preset CreateDefault(bool fillFirst)
        {
            Preset preset = new Preset();
            if (fillFirst)
            {
                preset.Name = "Výchozí";
                preset.PresetBaseDate = DateTime.Now;
                preset.PresetTimeRange = new TimeRange(preset.CurrentBaseDate.Date, preset.CurrentBaseDate);
                preset.CurrentBaseDate = preset.PresetBaseDate;
                preset.CurrentTimeRange = preset.PresetTimeRange;

            }
            return preset;
        }
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.Name;
        }
        /// <summary>
        /// Vyprázdní obsah, uvede jej do defaultního stavu
        /// </summary>
        internal void Clear()
        {
            this.ID = "";
            this.Name = "";
            this.PresetBaseDate = DateTime.MinValue;
            this.PresetTimeRange = TimeRange.Empty;
            this.CurrentBaseDate = DateTime.MinValue;
            this.CurrentTimeRange = TimeRange.Empty;
            this.IsInputAplTrace = true;
            this.IsInputBlockingLog = true;
            this.IsInputThreadsTrace = true;
            this.IsInputLockTrace = true;
            this.IsInputAllFiles = false;
            this.ProcessPackDir = "";
            this.ProcessUseThreadCount = System.Environment.ProcessorCount;
            this.ProcessUseThreadCount = (this.ProcessUseThreadCount < 1 ? 1 : (this.ProcessUseThreadCount > 8 ? 8 : this.ProcessUseThreadCount));

            this.InputsClear();
        }
        #endregion
        #region Public property
        /// <summary>
        /// ID, slouží pro identifikaci presetu při nevizuálním volání
        /// </summary>
        internal string ID { get; set; }
        /// <summary>
        /// Název presetu, pro uživatele
        /// </summary>
        internal string Name { get; set; }
        /// <summary>
        /// Preset je smazán, ale přesto je prozatím zachován. Jen se s ním nepracuje.
        /// </summary>
        internal bool Deleted { get; set; }
        /// <summary>
        /// Systémové datum při poslední aktualizaci časového rozsahu PresetTimeRange.
        /// Používá se pro přednastavení CurrentTimeRange pro aktuální CurrentBaseDate.
        /// </summary>
        internal DateTime PresetBaseDate { get; set; }
        /// <summary>
        /// Časový rozsah platný při poslední aktualizaci, vztahuje se k PresetBaseDate.
        /// Používá se pro přednastavení CurrentTimeRange pro aktuální CurrentBaseDate.
        /// </summary>
        internal TimeRange PresetTimeRange { get; set; }
        /// <summary>
        /// Aktuální systémové datum pro nastavení CurrentTimeRange 
        /// podle předchozího nastavení (PresetBaseDate a PresetTimeRange).
        /// </summary>
        internal DateTime CurrentBaseDate { get; set; }
        /// <summary>
        /// Časový rozsah načítaných souborů.
        /// </summary>
        internal TimeRange CurrentTimeRange { get; set; }
        /// <summary>
        /// Načítat vstupní soubory všechny = bez ohledu na jejich čas
        /// </summary>
        internal bool CurrentTimeAll { get; set; }
        /// <summary>
        /// Soupis vstupních cest
        /// </summary>
        internal IEnumerable<PresetInputItem> Inputs { get { return this._InputList; } }
        /// <summary>
        /// Načítat všechny soubory
        /// </summary>
        internal bool IsInputAllFiles { get; set; }
        /// <summary>
        /// Načítat aplikační trace
        /// </summary>
        internal bool IsInputAplTrace { get; set; }
        /// <summary>
        /// Načítat logy blokování
        /// </summary>
        internal bool IsInputBlockingLog { get; set; }
        /// <summary>
        /// Načítat trace Threads
        /// </summary>
        internal bool IsInputThreadsTrace { get; set; }
        /// <summary>
        /// Načítat trace Locks
        /// </summary>
        internal bool IsInputLockTrace { get; set; }
        /// <summary>
        /// Režim zipování (typ nástroje + jeho režim)
        /// </summary>
        internal PackMode PackMode { get; set; }
        /// <summary>
        /// Ukládat zipy do tohoto lokálního adresáře
        /// </summary>
        internal string ProcessPackDir { get; set; }
        /// <summary>
        /// Režim kopírování
        /// </summary>
        internal MoveMode MoveMode { get; set; }
        /// <summary>
        /// Výsledek kopírovat sem
        /// </summary>
        internal string ProcessMoveDir { get; set; }
        /// <summary>
        /// Počet souběžných vláken při zipování
        /// </summary>
        internal int ProcessUseThreadCount { get; set; }
        /// <summary>
        /// Tento preset běží v Sample modu = má význam pro zápis PerformaceData
        /// </summary>
        internal bool IsSampleMode { get; set; }
        #endregion
        #region Serializace
        public XElement Serial
        {
            get { return this._GetSerial(); }
            set { this._SetSerial(value); }
        }
        private XElement _GetSerial()
        {
            XElement element = new XElement("preset");
            element.AddAttribute("ID", this.ID);
            element.AddAttribute("Name", this.Name);
            element.AddAttribute("Deleted", this.Deleted);
            element.AddAttribute("PresetBaseDate", this.PresetBaseDate);
            element.AddAttribute("PresetTimeRange", this.PresetTimeRange);
            element.AddAttribute("CurrentBaseDate", this.CurrentBaseDate);
            element.AddAttribute("CurrentTimeRange", this.CurrentTimeRange);
            element.AddAttribute("CurrentTimeAll", this.CurrentTimeAll);
            element.AddAttribute("IsInputAllFiles", this.IsInputAllFiles);
            element.AddAttribute("IsInputAplTrace", this.IsInputAplTrace);
            element.AddAttribute("IsInputBlockingLog", this.IsInputBlockingLog);
            element.AddAttribute("IsInputThreadsTrace", this.IsInputThreadsTrace);
            element.AddAttribute("IsInputLockTrace", this.IsInputLockTrace);
            element.AddAttribute("PackMode", Convertor.EnumToString<PackMode>(this.PackMode));
            element.AddAttribute("ProcessPackDir", this.ProcessPackDir);
            element.AddAttribute("MoveMode", Convertor.EnumToString<MoveMode>(this.MoveMode));
            element.AddAttribute("ProcessMoveDir", this.ProcessMoveDir);
            element.AddAttribute("ProcessUseThreadCount", this.ProcessUseThreadCount);

            XElement inputList = new XElement("input_list");
            foreach (PresetInputItem inputItem in this.Inputs)
                inputList.Add(inputItem.Serial);
            element.Add(inputList);

            return element;
        }
        private void _SetSerial(XElement element)
        {
            this.Clear();
            if (element == null || element.Name.LocalName != "preset")
                return;
            this.ID = element.GetAttributeString("ID");
            this.Name = element.GetAttributeString("Name");
            this.Deleted = element.GetAttributeBoolean("Deleted");
            this.PresetBaseDate = element.GetAttributeDateTime("PresetBaseDate");
            this.PresetTimeRange = element.GetAttributeTimeRange("PresetTimeRange");
            this.CurrentBaseDate = element.GetAttributeDateTime("CurrentBaseDate");
            this.CurrentTimeRange = element.GetAttributeTimeRange("CurrentTimeRange");
            this.CurrentTimeAll = element.GetAttributeBoolean("CurrentTimeAll");
            this.IsInputAllFiles = element.GetAttributeBoolean("IsInputAllFiles");
            this.IsInputAplTrace = element.GetAttributeBoolean("IsInputAplTrace");
            this.IsInputBlockingLog = element.GetAttributeBoolean("IsInputBlockingLog");
            this.IsInputThreadsTrace = element.GetAttributeBoolean("IsInputThreadsTrace");
            this.IsInputLockTrace = element.GetAttributeBoolean("IsInputLockTrace");
            this.PackMode = Convertor.StringToEnum<PackMode>(element.GetAttributeString("PackMode"), Downloader.PackMode.WinCabMin);
            this.ProcessPackDir = element.GetAttributeString("ProcessPackDir");
            this.MoveMode = Convertor.StringToEnum<MoveMode>(element.GetAttributeString("MoveMode"), Downloader.MoveMode.None);
            this.ProcessMoveDir = element.GetAttributeString("ProcessMoveDir");
            this.ProcessUseThreadCount = element.GetAttributeInt32("ProcessUseThreadCount");

            XElement inputList = element.Element("input_list");
            if (inputList != null)
            {
                foreach (XElement input in inputList.Elements())
                {
                    PresetInputItem inputItem = PresetInputItem.CreateFrom(input);
                    if (inputItem != null)
                        this.InputsAdd(inputItem);
                }
            }
        }
        #endregion
        #region Klouzavá práce s časovou osou
        /// <summary>
        /// Uloží do sebe data o aktuálně vybraném časovém úseku.
        /// Ukládá vybraný časový úsek a aktuální datum.
        /// Následně metoda CurrentTimeGet dokáže vytvořit a vrátit časový úsek pro libovolné jiné datum.
        /// </summary>
        /// <param name="timeRange"></param>
        internal void CurrentTimeSet(TimeRange timeRange)
        {
            this.CurrentTimeRange = timeRange;
            this.CurrentBaseDate = DateTime.Now;
            this.PresetTimeRange = timeRange;
            this.PresetBaseDate = DateTime.Now;
        }
        internal TimeRange CurrentTimeGet()
        {
            DateTime now = DateTime.Now;
            if (this.PresetTimeRange.IsEmpty || this.PresetBaseDate.Year < 2000)
                return new TimeRange(now.Date, now);

            TimeSpan preDay = this.PresetTimeRange.Begin.Date - this.PresetBaseDate.Date;  // Počet dnů od začátku PresetTimeRange do dne, kdy byl používán (PresetBaseDate), typicky bude záporné
            DateTime beginDate = now.Date.Add(preDay);                                     // Den (s časem 00:00), který je ode dneška dozadu vzdálen stejně, jako PresetTimeRange od PresetBaseDate
            DateTime begin = beginDate.Add(this.PresetTimeRange.Begin.TimeOfDay);          // Přidáme čas
            DateTime end = begin.AddSeconds(this.PresetTimeRange.Seconds);
            return new TimeRange(begin, end);
        }
        #endregion
        #region Položky Inputs
        internal void InputsClear()
        {
            this._InputList.Clear();
        }
        internal void InputsAdd(PresetInputItem input)
        {
            input.Parent = this;
            this._InputList.Add(input);
        }
        private List<PresetInputItem> _InputList;
        #endregion
        #region Řídící rutina běhu
        /// <summary>
        /// Metoda najde zpracovávané soubory, a podle aktuální metody pakování odhadne výsledný čas a velikost dat
        /// </summary>
        /// <returns></returns>
        internal string ScanEstimatedWork()
        {
            this.PrepareToRun();

            PackMode packMode = this.PackMode;
            if (packMode == Downloader.PackMode.None) return "";

            PackInfo workPackInfo = PackInfo.Create(this.PackMode);
            int estimatedSeconds = (int)workPackInfo.CalculateEstimateSeconds(this.PreRunFileLength);
            int estimatedMinute = estimatedSeconds / 60;
            int estimatedSecond = estimatedSeconds - (60 * estimatedMinute);
            string estimatedTime = "";
            if (estimatedMinute > 0)
                estimatedTime = estimatedMinute.ToString() + "m:" + estimatedSecond.ToString("00") + "s";
            else
                estimatedTime = estimatedSeconds.ToString() + "s";

            long estimatedPackSize = workPackInfo.CalculateEstimateSize(this.PreRunFileLength);

            string result = "Vstupní soubory: počet=" + this.PreRunFileCount.ToString() +
                "; velikost=" + ((decimal)this.PreRunFileLength / (decimal)SIZE_MB).ToString("### ##0.00") + " MB" + Environment.NewLine +
                "Očekávaný čas=" + estimatedTime +
                "; velikost=" + ((decimal)estimatedPackSize / (decimal)SIZE_MB).ToString("### ##0.00") + " MB";

            return result;
        }
        internal void PrepareToRun()
        {
            this.AllFileInfoList = new List<System.IO.FileInfo>();
            this.FileInfoList = new List<System.IO.FileInfo>();
            this.PreRunFileCount = 0;
            this.PreRunFileLength = 0;
            foreach (PresetInputItem input in this.Inputs)
            {
                input.PrepareToRun();
                this.AllFileInfoList.AddRange(input.AllFileInfoList);
                this.FileInfoList.AddRange(input.FileInfoList);
                this.PreRunFileCount += input.PreRunFileCount;
                this.PreRunFileLength += input.PreRunFileLength;
            }
        }
        protected const long SIZE_KB = 1024L;
        protected const long SIZE_MB = SIZE_KB * SIZE_KB;
        protected const long SIZE_GB = SIZE_KB * SIZE_KB * SIZE_KB;
        internal List<System.IO.FileInfo> AllFileInfoList { get; private set; }
        internal List<System.IO.FileInfo> FileInfoList { get; private set; }
        internal int PreRunFileCount { get; private set; }
        internal long PreRunFileLength { get; private set; }
        private System.Threading.Thread WorkingThread;
        internal void Run(AsyncWorkCallbackHandler callbackHandler)
        {
            this.CallbackHandler = callbackHandler;
            this.ErrorSummary = "";

            // Spuštění hlavního řídícího threadu:
            this.WorkingThread = new System.Threading.Thread(this._RunThread);
            this.WorkingThread.IsBackground = true;
            this.WorkingThread.Name = "pack_main";
            this.WorkingThread.Priority = System.Threading.ThreadPriority.Lowest;
            this.WorkingThread.TrySetApartmentState(System.Threading.ApartmentState.MTA);
            
            this.WorkingThread.Start();
        }
        private void _RunThread()
        {
            // Main metoda threadu na pozadí
            try
            {
                this.OnCallBack(AsyncWorkCallbackArgs.Start());

                // Fáze 1: příprava (nápočet délky souborů a iniciace progresu):
                this.PrepareToRun();
                this.PrepareData();

                // Fáze 2: řízení zipování (zipování probíhá v separátních threadech v jednotlivých inputech):
                this.PackRun();

                // Fáze 3: zpracování zipů:
                this.MoveRun();

                // Dokončení = oznámení volajícímu:
                this.OnCallBack(AsyncWorkCallbackArgs.Success());
            }
            catch (Exception exc)
            {
                this.ErrorSummary += exc.Message + Environment.NewLine;
                this.OnCallBack(AsyncWorkCallbackArgs.Error(exc));
            }
            finally
            {
                this.CallbackHandler = null;
            }
        }
        private void PrepareData()
        {
            string outDir = this.ProcessPackDir;
            if (String.IsNullOrEmpty(outDir))
                throw new InvalidOperationException("Pracovní adresář není vyplněn.");
            if (!System.IO.Directory.Exists(outDir))
                System.IO.Directory.CreateDirectory(outDir);

            MoveInfo moveInfo = MoveInfo .Create(this.MoveMode);
            float moveTime = moveInfo.RelativeTime;
            decimal transition = (moveTime <= 0f ? 1.0m : (moveTime >= 0.85f ? 0.15m : (decimal)(1f - moveTime)));
            this._InputToMoveTransition = transition;
        }
        #endregion
        #region AsyncWorkCallback Handler & spol
        /// <summary>
        /// Řídící mechanismus spouštění pakování jednotlivých inputů (adresářů)
        /// </summary>
        private void PackRun()
        {
            if (this._InputList.Count == 0)
            {
                return;
            }
            int loops = 6 * (this._InputList.Count + this.ProcessUseThreadCount);
            int wd = loops;
            this._AsyncDoneEvent = new System.Threading.AutoResetEvent(false);
            while (true)
            {
                int notDoneCount = this._InputNotDoneCount;
                if (notDoneCount == 0)
                    // Už není nic dalšího ke zpracování, skončíme:
                    break;
                if (--wd <= 0)
                {
                    Steward.AuditWarning("Řídící rutina Preset.PackRun() selhala, nedokázala vyřešit ani ve " + loops.ToString() + " cyklech všechny vstupní adresáře.");
                    break;
                }

                this._AsyncDoneEvent.Reset();    // Vynuluji budíček, neboť teď jsem zcela vzhůru, ale pokud by zazvonil někdy od teď, tak nás to bude zajímat.
                bool isAnyWorking = false;
                int workingCount = this._InputWorkingCount;
                if (workingCount < this.ProcessUseThreadCount)
                {
                    PresetInputItem input = this.Inputs.FirstOrDefault(i => i.IsWaiting);
                    if (input != null)
                    {   // Pokud mám další práci, jdu na ni; okamžitě se přepne do stavu Start (=už je započítaná ve _InputWorkingCount):
                        input.Run(this.AsyncPackCallback);
                        isAnyWorking = true;
                    }
                }

                workingCount = this._InputWorkingCount;
                if (workingCount >= this.ProcessUseThreadCount || !isAnyWorking)
                {   // Pokud jsem počítač dostatečně zaměstnal, jdu si zdřímnout:
                    this._AsyncDoneEvent.WaitOne(TimeSpan.FromSeconds(300));       // Pro jistotu si natáhnu budíček za pár minut. Když se mezitím nic nestane, zjistím to ve smyčce a zase půjdu spát.
                }
            }
        }
        /// <summary>
        /// Obsluha událostí přicházejících z podřízených pracovních vláken (AsyncWorkCallbackHandler)
        /// </summary>
        /// <param name="caller"></param>
        /// <param name="args"></param>
        private void AsyncPackCallback(object caller, AsyncWorkCallbackArgs args)
        {
            switch (args.State)
            {
                case AsyncWorkState.Progress:
                    this.AsyncPackProgress(caller as PresetInputItem, args);
                    break;
                case AsyncWorkState.Error:
                    this.ErrorSummary += args.Exception.Message + Environment.NewLine;
                    // Skončili jsme. Pokud řídící rutina čeká v metodě _RunBgrTry(), probudíme ji:
                    this._AsyncDoneEvent.Set();
                    break;
                case AsyncWorkState.Success:
                    // Skončili jsme. Pokud řídící rutina čeká v metodě _RunBgrTry(), probudíme ji:
                    this._AsyncDoneEvent.Set();
                    break;
            }
        }
        /// <summary>
        /// Zobrazuje progres při pakování (formátuje data a texty)
        /// </summary>
        /// <param name="input"></param>
        /// <param name="args"></param>
        private void AsyncPackProgress(PresetInputItem input, AsyncWorkCallbackArgs args)
        {
            if (args.ProgressRatio.HasValue)
            {
                string message1 = this._InputTotalPackMessage;
                string message2 = this._InputDetailPackMessage;
                string message = message1 + "  (" + message2 + ")";
                this.OnCallBack(AsyncWorkCallbackArgs.Progress(_InputBytesPackedRatio, message));
            }
            if (args.ProgressMessage != null)
            {
                this.OnCallBack(AsyncWorkCallbackArgs.Progress(args.ProgressMessage));
            }
        }
        /// <summary>
        /// Řídící mechanismus spouštění kopírování výsledků (zipů)
        /// </summary>
        private void MoveRun()
        {
            this._AsyncDoneEvent = new System.Threading.AutoResetEvent(false);
            this._AsyncDoneEvent.Reset();           // Vynuluji budíček, neboť teď jsem zcela vzhůru, ale pokud by zazvonil někdy od teď, tak nás to bude zajímat.
                
            List<System.IO.FileInfo> files = this.Inputs.Select<PresetInputItem, System.IO.FileInfo>(input => input.ResultFile).Where(fi => fi != null).ToList();
            this._MoveInfo = MoveInfo.Create(this.MoveMode);
            this._MoveInfo.Run(files, this.ProcessMoveDir, this.AsyncMoveCallback);

            // Pojistím se proti zatuhnutí při výstupu:
            this._MoveTimer = new Timer();
            this._MoveTimer.Tick += new EventHandler(_MoveTimer_Tick);
            this._MoveTimer.Interval = 120000;      // Po 120 sekundách spustí Timer svou akci, která odstřelí kopírovací vlákno. Pokud ale vlákno bude pravidelně volat metodu AsyncMoveCallback(), pak nebude odstřeleno.
            this._MoveTimer.Start();

            this._AsyncDoneEvent.WaitOne();         // Tady čekám na dokončení kopírování. Zde není budíček, zde máme aktivní odstřelovač (_MoveTimer).
            this._MoveTimer.Stop();
            this._MoveTimer = null;

            this._MoveInfo = null;
        }
        /// <summary>
        /// Když se čas naplnil
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _MoveTimer_Tick(object sender, EventArgs e)
        {
            this._MoveTimer.Stop();
            if (!Steward.DebugMode)                 // Pokud jsme v ostrém režimu, pak odstřelovač zastřelí výkonné vlákno:
            {
                this._MoveInfo.Abort();
                this._AsyncDoneEvent.Set();
            }
        }
        /// <summary>
        /// Obsluha událostí přicházejících z podřízených pracovních vláken (AsyncWorkCallbackHandler)
        /// </summary>
        /// <param name="caller"></param>
        /// <param name="args"></param>
        private void AsyncMoveCallback(object caller, AsyncWorkCallbackArgs args)
        {
            // Zresetujeme odstřelovače:
            if (this._MoveTimer != null)
                this._MoveTimer.Stop();

            // Vyřešíme aktivitu:
            bool timerOff = false;
            switch (args.State)
            {
                case AsyncWorkState.Progress:
                    this.AsyncMoveProgress(caller as PresetInputItem, args);
                    break;
                case AsyncWorkState.Error:
                case AsyncWorkState.Success:
                    // Skončili jsme. Pokud řídící rutina čeká v metodě _RunBgrTry(), probudíme ji:
                    this._AsyncDoneEvent.Set();
                    timerOff = true;
                    break;
            }

            if (!timerOff && this._MoveTimer != null)
            {
                this._MoveTimer.Interval = 120000;
                this._MoveTimer.Start();
            }
        }
        private MoveInfo _MoveInfo;
        private Timer _MoveTimer;
        private void AsyncMoveProgress(PresetInputItem input, AsyncWorkCallbackArgs args)
        {
            if (args.ProgressRatio.HasValue)
            {
                decimal progress = _GetMoveProgressRatio(args.ProgressRatio.Value);
                this.OnCallBack(AsyncWorkCallbackArgs.Progress(progress, args.ProgressMessage));
            }
            else if (args.ProgressMessage != null)
            {
                this.OnCallBack(AsyncWorkCallbackArgs.Progress(args.ProgressMessage));
            }
        }
        /// <summary>
        /// Počet vstupů, které jsou ve stavu IsWaiting (=stav None = čeká na Start)
        /// </summary>
        private int _InputWaitingCount { get { return this.Inputs.Count(i => i.IsWaiting); } }
        /// <summary>
        /// Počet vstupů, které jsou ve stavu IsWorking (=stav Start nebo Progress)
        /// </summary>
        private int _InputWorkingCount { get { return this.Inputs.Count(i => i.IsWorking); } }
        /// <summary>
        /// Počet vstupů, které ještě nejsou ve stavu IsDone (IsDone je stav Error nebo Success)
        /// </summary>
        private int _InputNotDoneCount { get { return this.Inputs.Count(i => !i.IsDone); } }
        /// <summary>
        /// Součet délek PreRunFileLength z položek Inputs, které jsou IsDone
        /// </summary>
        private long _InputDonePreRunFileLength { get { return this.Inputs.Sum(i => (i.IsDone ? i.PreRunFileLength : 0L)); } }
        private long _InputBytesTotal { get { return this.Inputs.Sum(i => i.InputBytesTotal); } }
        private long _InputBytesPacked { get { return this.Inputs.Sum(i => i.InputBytesProcessed); } }
        private long _MoveBytesTotal { get { return this.Inputs.Sum(i => i.MoveBytesTotal); } }
        private long _MoveBytesProcessed { get { return this.Inputs.Sum(i => i.MoveBytesProcessed); } }
        private decimal _InputToMoveTransition { get; set; }
        private decimal _InputBytesPackedRatio { get { return this._InputToMoveTransition * ((decimal)this._InputBytesPacked / (decimal)this._InputBytesTotal); } }
        private decimal _GetMoveProgressRatio(decimal moveProgress)
        {
            return (this._InputToMoveTransition + (1m - this._InputToMoveTransition) * (moveProgress < 0m ? 0m : (moveProgress > 1m ? 1m : moveProgress)));
        }
        private string _InputTotalPackMessage
        {
            get
            {
                long bytesTotal = this._InputBytesTotal;
                long bytesPacked = this._InputBytesPacked;
                decimal ratio = (decimal)bytesPacked / (decimal)bytesTotal;
                string message = "Pack: " + FormatProcessedByte(bytesPacked, bytesTotal);
                return message;
            }
        }
        private string _InputDetailPackMessage
        {
            get
            {
                string message = "";
                foreach (PresetInputItem input in this.Inputs.Where(i => i.IsWorking))
                    message += ", " + input.PackMessage;
                return (message.Length == 0 ? message : message.Substring(2));
            }
        }
        private decimal _InputBytesMoveRatio { get { return (decimal)this._MoveBytesProcessed / (decimal)this._MoveBytesTotal; } }
        internal static string FormatProcessedByte(long bytesPacked, long bytesTotal)
        {
            string fmt = "### ### ### ##0";
            decimal bp = 0m;
            decimal bt = 0m;
            string unit = "";

            if (bytesTotal < 10000)
            {
                bp = ((decimal)bytesPacked);
                bt = ((decimal)bytesTotal);
                unit = "B";
            }
            else if (bytesTotal < 10000 * SIZE_KB)
            {
                bp = ((decimal)bytesPacked) / (decimal)SIZE_KB;
                bt = ((decimal)bytesTotal) / (decimal)SIZE_KB;
                unit = "KB";
            }
            else if (bytesTotal < 10000 * SIZE_MB)
            {
                bp = ((decimal)bytesPacked) / (decimal)SIZE_MB;
                bt = ((decimal)bytesTotal) / (decimal)SIZE_MB;
                unit = "MB";
            }
            else
            {
                bp = ((decimal)bytesPacked) / (decimal)SIZE_GB;
                bt = ((decimal)bytesTotal) / (decimal)SIZE_GB;
                unit = "GB";
            }

            return bp.ToString(fmt).Trim() + "/" + bt.ToString(fmt).Trim() + " " + unit;
        }
        /// <summary>
        /// Budíček zdejšího threadu z threadu inputů
        /// </summary>
        private System.Threading.AutoResetEvent _AsyncDoneEvent;
        /// <summary>
        /// Úložiště handleru AsyncWorkCallback do nadřízené úrovně
        /// </summary>
        private AsyncWorkCallbackHandler CallbackHandler;
        /// <summary>
        /// Sumář chyb
        /// </summary>
        internal string ErrorSummary { get; set; }
        /// <summary>
        /// Vyvolávač handleru AsyncWorkCallback do nadřízené úrovně
        /// </summary>
        /// <param name="args"></param>
        protected void OnCallBack(AsyncWorkCallbackArgs args)
        {
            if (this.CallbackHandler != null)
                this.CallbackHandler(this, args);
        }
        #endregion
        #region Načítání seznamu vstupních souborů, filtrování podle parametrů
        /// <summary>
        /// Metoda zjistí, zda konkrétní soubor vyhovuje zadaným podmínkám.
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        internal bool InputFileSatisfyConditions(System.IO.FileInfo file)
        {
            if (file == null) return false;
            if (!this.CurrentTimeAll && !this.CurrentTimeRange.Contain(file.LastWriteTime)) return false;

            if (this.IsInputAllFiles) return true;

            string fileName = file.Name.ToLower();
            string extension = System.IO.Path.GetExtension(fileName);
            string name = System.IO.Path.GetFileNameWithoutExtension(fileName);

            if (this.InputFileSatisfyConditionOne(this.IsInputAplTrace, name, @"^Trace_(.*?)_(\d*?)_(\d*?)$", extension, ".csv")) return true;               // Regex popisuje jméno například: Trace_KordPlusA1_120828_002
            if (this.InputFileSatisfyConditionOne(this.IsInputBlockingLog, name, @"^Trace_(.*?)_(\d*?)_blocking_(.*?)$", extension, ".csv")) return true;    // Regex popisuje jméno například: Trace_KordPlusA1_120828_blocking_G_Kordarna
            if (this.InputFileSatisfyConditionOne(this.IsInputThreadsTrace, name, @"^Trace_(.*?)_(\d*?)_threads$", extension, ".csv")) return true;          // Regex popisuje jméno například: Trace_KordPlusA3_120828_threads
            if (this.InputFileSatisfyConditionOne(this.IsInputLockTrace, name, @"^Trace_(.*?)_(\d*?)_locks$", extension, ".csv")) return true;               // Regex popisuje jméno například: Trace_KordPlusA3_120828_locks

            return false;
        }
        /// <summary>
        /// Vrací: false, když tento typ souboru nechci načítat.
        /// Pokud jej chci načítat, pak vrací true tehdy, když jméno a přípona souboru odpovídá zadaným hodnotám.
        /// </summary>
        /// <param name="condition">Chci jej načítat?</param>
        /// <param name="name">Název (lower)</param>
        /// <param name="namePattern">Regex obsahující pattern jména souboru bez přípony</param>
        /// <param name="extension">Přípona včetně tečky (".csv"), (lower)</param>
        /// <param name="extensionPattern">Požadovaná přípona, nebo null když nechci testovat</param>
        /// <returns></returns>
        private bool InputFileSatisfyConditionOne(bool condition, string name, string namePattern, string extension, string extensionPattern)
        {
            if (!condition) return false;                                                  // Dané soubory nechci načítat, vrátím false

            // Soubory tohoto typu chci načítat; zjistím, zda dané jméno odpovídá očekávanému vzorci:
            if (extensionPattern != null && extension.ToLower() != extensionPattern) return false;         // Soubor má jinou příponu, vrátím false

            System.Text.RegularExpressions.RegexOptions options = System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Singleline | System.Text.RegularExpressions.RegexOptions.ExplicitCapture;
            System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(namePattern, options);
            return regex.IsMatch(name);
        }
        #endregion
    }
    /// <summary>
    /// Třída popisující jedno vstupní umístění (adresář, aktivita, případně další specifikace)
    /// </summary>
    internal class PresetInputItem
    {
        #region Konstrukce a clear
        internal PresetInputItem()
        {
            this.Clear();
        }
        internal static PresetInputItem CreateFrom(XElement element)
        {
            if (element == null || element.Name.LocalName != "input") return null;
            PresetInputItem inputItem = new PresetInputItem();
            inputItem.Serial = element;
            return inputItem;
        }
        internal static PresetInputItem CreateFrom(PresetInputItem original)
        {
            if (original == null) return null;
            PresetInputItem inputItem = new PresetInputItem();
            inputItem.Serial = original.Serial;
            return inputItem; 
        }
        internal static PresetInputItem CreateFrom(DataGridViewRow row)
        {
            if (row == null) return null;
            PresetInputItem inputItem = new PresetInputItem();
            inputItem.DataGridRow = row;
            return inputItem; 
        }
        internal static PresetInputItem CreateFrom(string editTextRow)
        {
            if (String.IsNullOrEmpty(editTextRow)) return null;
            PresetInputItem inputItem = new PresetInputItem();
            inputItem.EditTextRow = editTextRow;
            return inputItem;
        }
        public override string ToString()
        {
            return this.Path;
        }
        internal void Clear()
        {
            this.Active = true;
            this.Path = "";
            this.ZipName = "";
            this.WorkState = AsyncWorkState.None;
            this.ResultException = null;
            this.ResultFile = null;
        }
        #endregion
        #region Public property
        internal Preset Parent { get; set; }
        internal bool Active { get; set; }
        internal string Path { get; set; }
        internal string ZipName { get; set; }
        internal string ZipFile { get; set; }
        internal string Error { get; set; }
        internal bool IsEmpty { get { return (String.IsNullOrEmpty(this.Path) && String.IsNullOrEmpty(this.ZipName)); } }
        #endregion
        #region Serializace
        public XElement Serial
        {
            get { return this._GetSerial(); }
            set { this._SetSerial(value); }
        }
        private XElement _GetSerial()
        {
            XElement element = new XElement("input");
            element.AddAttribute("Active", this.Active);
            element.AddAttribute("Path", this.Path);
            element.AddAttribute("ZipName", this.ZipName);
            return element;
        }
        private void _SetSerial(XElement element)
        {
            this.Active = element.GetAttributeBoolean("Active");
            this.Path = element.GetAttributeString("Path");
            this.ZipName = element.GetAttributeString("ZipName");
        }
        #endregion
        #region Podpora editace - DataGridViewRow a EditTextRow, ConvertGridToText
        #region DataGridViewRow
        internal DataGridViewRow DataGridRow
        {
            get { return this._GetDataGridRow(); }
            set { this._SetDataGridRow(value); }
        }
        private DataGridViewRow _GetDataGridRow()
        {
            DataGridViewRow row = new DataGridViewRow();
            row.Cells.Add(new DataGridViewCheckBoxCell(false) { EditingCellFormattedValue = this.Active, FalseValue = false, TrueValue = true, Value = this.Active });
            row.Cells.Add(new DataGridViewTextBoxCell() { Value = this.Path });
            row.Cells.Add(new DataGridViewButtonCell());
            row.Cells.Add(new DataGridViewTextBoxCell() { Value = this.ZipName });
            return row;
        }
        private void _SetDataGridRow(DataGridViewRow row)
        {
            if (row.Cells.Count < 4) return;
            DataGridViewCheckBoxCell cell0 = row.Cells[0] as DataGridViewCheckBoxCell;
            DataGridViewTextBoxCell cell1 = row.Cells[1] as DataGridViewTextBoxCell;
            DataGridViewTextBoxCell cell3 = row.Cells[3] as DataGridViewTextBoxCell;
            if (cell0 != null && cell1 != null && cell3 != null)
            {
                bool isActive = false;
                if (cell0.EditingCellFormattedValue is bool)
                    isActive = (bool)cell0.EditingCellFormattedValue;
                if (cell0.Value is bool)
                    isActive |= (bool)cell0.Value;
                this.Active = isActive;
                this.Path = (string)cell1.Value;
                this.ZipName = (string)cell3.Value;
            }
        }
        #endregion
        #region EditTextRow
        internal string EditTextRow
        {
            get { return this._GetEditTextRow(); }
            set { this._SetEditTextRow(value); }
        }
        private string _GetEditTextRow()
        {
            string tab = "\t";
            string text = (this.Active ? "*" : "") + tab + this.Path + tab + this.ZipName;
            return text;
        }
        private void _SetEditTextRow(string value)
        {
            this.Clear();
            this.Active = false;

            if (!String.IsNullOrEmpty(value))
            {
                Queue<string> items = new Queue<string>(value.Split('\t').Where(item => !String.IsNullOrEmpty(item)));
                if (items.Count > 0)
                {
                    // * = Active:
                    string item = items.Dequeue();
                    if (item == "*")
                    {
                        this.Active = true;
                        item = "";
                    }
                    else if (item.StartsWith("*"))
                    {
                        this.Active = true;
                        item = (item.Length > 1 ? item.Substring(1).Trim() : "");
                    }

                    // Path:
                    if (item == "" && items.Count > 0)
                        item = items.Dequeue();
                    this.Path = item;

                    // ZipName:
                    if (items.Count > 0)
                    {
                        item = items.Dequeue();
                        this.ZipName = item;
                    }
                }
            }
        }
        #endregion
        #region ConvertGridToText, ConvertTextToGrid
        internal static void ConvertGridToText(DataGridViewRowCollection rows, out string text)
        {
            string eol = Environment.NewLine;
            text = "";
            foreach (DataGridViewRow row in rows)
            {
                PresetInputItem input = PresetInputItem.CreateFrom(row);
                if (input != null && !input.IsEmpty)
                    text += input.EditTextRow + eol;
            }
        }
        internal static void ConvertTextToGrid(string text, DataGridViewRowCollection rows)
        {
            rows.Clear();
            string[] lines = text.Split('\r', '\n');
            foreach (string line in lines)
            {
                if (!String.IsNullOrEmpty(line))
                {
                    PresetInputItem input = PresetInputItem.CreateFrom(line);
                    if (input != null && !input.IsEmpty)
                        rows.Add(input.DataGridRow);
                }
            }
        }
        #endregion
        #endregion
        #region Zpracování dat
        internal List<System.IO.FileInfo> AllFileInfoList { get; private set; }
        internal List<System.IO.FileInfo> FileInfoList { get; private set; }
        internal int PreRunFileCount { get; private set; }
        internal long PreRunFileLength { get; private set; }
        internal void PrepareToRun()
        {
            this.LoadFiles(true);
            this.PreRunFileCount = this.FileInfoList.Count;
            this.PreRunFileLength = this.FileInfoList.Sum(fi => fi.Length);
            this.InputBytesTotal = this.PreRunFileLength;
            this.InputBytesProcessed = 0;
        }
        internal void Run(AsyncWorkCallbackHandler callbackHandler)
        {
            List<System.IO.FileInfo> files = this.FileInfoList;
            if (files.Count == 0)
            {
                this.WorkState = AsyncWorkState.Success;
                return;
            }
            this.WorkBegin = DateTime.Now;
            this.WorkState = AsyncWorkState.Start;
            this.InputBytesTotal = files.Sum(fi => fi.Length);
            this.InputBytesProcessed = 0L;
            this.MoveBytesTotal = 0L;
            this.MoveBytesProcessed = 0L;
            this.CallbackHandler = callbackHandler;
            this.WorkPackInfo = PackInfo.Create(this.Parent.PackMode);

            string outName = System.IO.Path.Combine(this.Parent.ProcessPackDir, this.ZipName);
            this.WorkPackInfo.Run(files, outName, this.AsyncWorkCallback);         // Asynchronní běh
        }
        protected PackInfo WorkPackInfo;
        /// <summary>
        /// Stav procesu
        /// </summary>
        public AsyncWorkState WorkState { get; protected set; }
        /// <summary>
        /// Input je ve stavu None (=čeká na Start)
        /// </summary>
        public bool IsWaiting { get { return (this.WorkState == AsyncWorkState.None); } }
        /// <summary>
        /// Input je ve stavu Working (=Start nebo Progress)
        /// </summary>
        public bool IsWorking { get { return (this.WorkState == AsyncWorkState.Start || this.WorkState == AsyncWorkState.Progress); } }
        /// <summary>
        /// Input je ve stavu Hotovo (=Error nebo Success)
        /// </summary>
        public bool IsDone { get { return (this.WorkState == AsyncWorkState.Error || this.WorkState == AsyncWorkState.Success); } }
        public string PackMessage
        {
            get
            {
                if (!this.IsWorking) return "";
                return System.IO.Path.GetFileNameWithoutExtension(this.ZipName) + ": " + Preset.FormatProcessedByte(this.InputBytesProcessed, this.InputBytesTotal);
            }
        }
        /// <summary>
        /// Výstupní soubor
        /// </summary>
        public System.IO.FileInfo ResultFile { get; protected set; }
        /// <summary>
        /// Chyba, k níž došlo
        /// </summary>
        public Exception ResultException { get; protected set; }
        public DateTime? WorkBegin { get; protected set; }
        public DateTime? WorkStart { get; protected set; }
        public DateTime? WorkEnd { get; protected set; }
        /// <summary>
        /// Kolik Byte má být celkem zapakováno (součet ze source souborů)
        /// </summary>
        public long InputBytesTotal { get; protected set; }
        /// <summary>
        /// Kolik Byte je již zapakováno (součet ze source souborů)
        /// </summary>
        public long InputBytesProcessed { get; protected set; }
        /// <summary>
        /// Kolik Byte má být přeneseno = velikost ZIPu
        /// </summary>
        public long MoveBytesTotal { get; protected set; }
        /// <summary>
        /// Kolik Byte je již přeneseno = progress
        /// </summary>
        public long MoveBytesProcessed { get; protected set; }
        /// <summary>
        /// Metoda vyhledá soubory, které se mají podle aktuální konfigurace načítat.
        /// Zohlední zdejší nastavení vstupního adresáře, nastavení presetu (časová osa a typy souborů).
        /// </summary>
        /// <returns></returns>
        internal void LoadFiles()
        {
            this.LoadFiles(false);
        }
        /// <summary>
        /// Metoda vyhledá soubory, které se mají podle aktuální konfigurace načítat.
        /// Zohlední zdejší nastavení vstupního adresáře, nastavení presetu (časová osa a typy souborů).
        /// </summary>
        /// <returns></returns>
        internal void LoadFiles(bool skipSort)
        {
            this.AllFileInfoList = new List<System.IO.FileInfo>();
            this.FileInfoList = new List<System.IO.FileInfo>();

            System.IO.DirectoryInfo dirInfo = new System.IO.DirectoryInfo(this.Path);
            if (!dirInfo.Exists) return;

            System.IO.SearchOption options = System.IO.SearchOption.TopDirectoryOnly;
            this.AllFileInfoList.AddRange(dirInfo.GetFiles("*.*", options));

            if (this.Active)
            {
                foreach (System.IO.FileInfo file in this.AllFileInfoList)
                {
                    if (this.Parent.InputFileSatisfyConditions(file))
                        this.FileInfoList.Add(file);
                }
            }

            if (!skipSort && this.FileInfoList.Count > 1)
                this.FileInfoList.Sort((a, b) => a.Name.CompareTo(b.Name));
        }
        #endregion
        #region AsyncWorkCallback Handler & spol
        /// <summary>
        /// Obsluha událostí přicházejících z pracovních vláken (AsyncWorkCallbackHandler)
        /// </summary>
        /// <param name="caller"></param>
        /// <param name="args"></param>
        private void AsyncWorkCallback(object caller, AsyncWorkCallbackArgs args)
        {
            PackInfo packInfo = caller as PackInfo;
            this.WorkState = args.State;
            switch (args.State)
            {
                case AsyncWorkState.Start:
                    this.WorkStart = DateTime.Now;
                    this.OnCallBack(args);
                    break;
                case AsyncWorkState.Progress:
                    if (args.ProgressRatio.HasValue)
                        this.InputBytesProcessed = (long)(args.ProgressRatio.Value * (decimal)this.InputBytesTotal);
                    this.OnCallBack(args);
                    break;
                case AsyncWorkState.Error:
                    // this.Parent.ErrorSummary += args.Exception.Message + Environment.NewLine();
                    this.ResultException = args.Exception;
                    this.InputBytesProcessed = this.InputBytesTotal;
                    this.OnCallBack(args);
                    break;
                case AsyncWorkState.Success:
                    this.WorkEnd = DateTime.Now;
                    this.ResultFile = packInfo.ResultFile;
                    this.MoveBytesTotal = this.ResultFile.Length;
                    this.InputBytesProcessed = this.InputBytesTotal;
                    if (this.Parent.IsSampleMode)
                        PackPerformance.AddSampleData(this.WorkPackInfo.PackMode, this.InputBytesTotal, this.MoveBytesTotal, (new TimeRange(this.WorkStart.Value, this.WorkEnd.Value).Seconds));
                    else
                        PackPerformance.AddRealData(this.WorkPackInfo.PackMode, this.InputBytesTotal, this.MoveBytesTotal, (new TimeRange(this.WorkStart.Value, this.WorkEnd.Value).Seconds));
                    this.OnCallBack(args);
                    break;
            }
        }
        /// <summary>
        /// Úložiště handleru AsyncWorkCallback do nadřízené úrovně
        /// </summary>
        private AsyncWorkCallbackHandler CallbackHandler;
        /// <summary>
        /// Vyvolávač handleru AsyncWorkCallback do nadřízené úrovně
        /// </summary>
        /// <param name="args"></param>
        protected void OnCallBack(AsyncWorkCallbackArgs args)
        {
            if (this.CallbackHandler != null)
                this.CallbackHandler(this, args);
        }        
        #endregion
    }
}
